//prova di sincro
package it.unibo.deis.lia.filters;

/**
 * <p>Copyright: Copyright (c) 2003</p>
 * @author Carlo Giannelli
 * @version 1.0
 */

import it.unibo.deis.lia.location.LocationOss;
import it.unibo.deis.lia.location.NamedLocationOss;

import java.util.*;


public class FilterManager{
	
	private IFilter filterFactory;
	
	// key: location identifier AP
	// value: filter
	private Hashtable<String,IFilter> filters=new Hashtable<String,IFilter>();
	
	private TreeMap<String,Vector<Double>> actualDataDB=new TreeMap<String,Vector<Double>>(new Hashtable<String,Vector<Double>>());
	private TreeMap<String,Vector<Double>> filteredDataDB=new TreeMap<String,Vector<Double>>(new Hashtable<String,Vector<Double>>());
	
	private int iteration=0;
	private int filterMemory=15;
	private boolean minimizeValueSet=false;
	// --------------
	// public methods
	// --------------
	public FilterManager(){
		this.filterFactory=new Identity();
	}
	public FilterManager(IFilter filter){
		this.filterFactory=filter;
		/*Hashtable<String,IFilter> newFilters=new Hashtable<String,IFilter>();
		Enumeration<String> filtersKeys=filters.keys();
		while(filtersKeys.hasMoreElements()){
			String bss=filtersKeys.nextElement();
			newFilters.put(bss, filterFactory.genFilter());
		}*/
	}
	
	public void setFilterMemory(int filterMemory){
		this.filterMemory=filterMemory;
	}
	public int getFilterMemory(){
		return filterMemory;
	}
	
	public void setMinimizeValueSet(boolean minimizeValueSet){
		this.minimizeValueSet=minimizeValueSet;
	}
	public boolean isMinimizeValueSet(){
		return minimizeValueSet;
	}
	
	public void feedPredictor(Vector<NamedLocationOss> locationList){
		if(minimizeValueSet) insertMinData(locationList);
		else insertAllData(locationList);
		
		doFiltering(filterMemory);//,0);
		
		if(iteration<filterMemory || !minimizeValueSet) iteration++;
	}

	public TreeMap<String,Vector<Double>> getActualDataDB(){
		return actualDataDB;
	}
	public TreeMap<String,Vector<Double>> getFilteredDataDB(){
		return filteredDataDB;
	}
	public String getFilterName(){
		return filterFactory.getClass().getName();
	}

	// ---------------
	// private methods
	// ---------------
	private void insertMinData(Vector<NamedLocationOss> locationList){
		// inserisco i valori dell'rssi nel db
		//int maxSize=harmonic*4;
		
		Vector<String> apListIdentifier=new Vector<String>();
		
		for(int i=0;i<locationList.size();i++){
			Vector<Double> vRssi;
			apListIdentifier.addElement(locationList.elementAt(i).getId());
			
			// se la Localita' rilevata e' nuova, aggiungo dei valori fittizi (null)
			// in modo da avere collezioni della medesima lunghezza
			if(!actualDataDB.containsKey(locationList.elementAt(i).getId())){
				vRssi= new Vector<Double>();
				//for(int x=0;x<iteration;x++){
				for(int x=0;x<filterMemory-1;x++){
					vRssi.addElement(null);
				}
				actualDataDB.put((locationList.elementAt(i)).getId(),vRssi);
				filters.put(locationList.elementAt(i).getId(),filterFactory.genFilter());
			}
			else{
				vRssi=actualDataDB.get(locationList.elementAt(i).getId());
			}
			vRssi.addElement(new Double(((LocationOss)locationList.elementAt(i)).getValue()));
			if(vRssi.size()>filterMemory)vRssi.remove(0);
		}
		
		// remove APs in the db but not currently visible
		Set<String> dbSet=actualDataDB.keySet();
		Iterator<String> it=dbSet.iterator();
		Vector<String> toRemove=new Vector<String>(); 
		while(it.hasNext()){
			boolean loactionAlreadyExist=false;
			String apIdentifier=it.next();
			for(int i=0;i<locationList.size();i++){
				if(locationList.elementAt(i).getId().equals(apIdentifier)){
					loactionAlreadyExist=true;
					break;
				}
			}
			if(!loactionAlreadyExist){
				toRemove.addElement(apIdentifier);
				//actualDataDB.remove(apIdentifier);
			}
		}
		for(int i=0;i<toRemove.size();i++){
			actualDataDB.remove(toRemove.elementAt(i));
		}	
	}
	
	/** 
   	*  
   	*  
   	*/
	private void insertAllData(Vector<NamedLocationOss> locationList){
		// inserisco i valori dell'rssi nel db
		
		Vector<String> apListIdentifier=new Vector<String>();
		
		for(int i=0;i<locationList.size();i++){
			Vector<Double> vRssi;
			apListIdentifier.addElement(locationList.elementAt(i).getId());
			//System.out.println("inserisciDati bbb "+elencoLocalita.elementAt(i));
			
			// se la Localita' rilevata e' nuova, aggiungo dei valori fittizi (null)
			// in modo da avere collezioni della medesima lunghezza
			if(!actualDataDB.containsKey(locationList.elementAt(i).getId())){
				vRssi= new Vector<Double>();
				for(int x=0;x<iteration;x++){
					vRssi.addElement(null);
				}
				actualDataDB.put((locationList.elementAt(i)).getId(),vRssi);
				filters.put(locationList.elementAt(i).getId(),filterFactory.genFilter());
			}
			else{
				vRssi=actualDataDB.get(locationList.elementAt(i).getId());
			}
			vRssi.addElement(new Double(((LocationOss)locationList.elementAt(i)).getValue()));
		}
		
		Set<String> dbSet=actualDataDB.keySet();
		Iterator<String> it=dbSet.iterator();
		while(it.hasNext()){
			boolean loactionAlreadyExist=false;
			String apIdentifier=it.next();
			for(int i=0;i<locationList.size();i++){
				if(locationList.elementAt(i).getId().equals(apIdentifier)){
					loactionAlreadyExist=true;
					break;
				}
			}
			if(!loactionAlreadyExist){
				// aggiungo null nei bss esistenti in db ma non rilevati dall'ultimo scan
				(actualDataDB.get(apIdentifier)).addElement(null);
			}
		}
	}
	
	/** 
   	*  
   	*/
	private void doFiltering(int numeroValori){//, double costante){
		// Il valore "costante" serve a rendere le previsioni piu' graduali.
		// Infatti quando le previsioni vengono fatte su numeri piccoli, basta una piccola variazioni
		// per ottenere una predizione a picco. Agginungendo una costante le predizioni dovrebbero essere
		// piu' regolari e simili con valori grandi e piccoli. Inoltre e' da ricordare che i valori
		// inseriti devono essere non negativi.
		// Un'idea per il valore di "costante" potrebbe essere 1/10 del massimo valore che puo' raggiungere
		// il dato da prevedere. Un valore troppo basso rende le previsioni instabili (troppi picchi)
		// mentre un valore troppo alto rende le previsioni lente. 

		// remove APs in the db but not currently visible
		Set<String> dbFilteredSet=filteredDataDB.keySet();
		Iterator<String> itFiltered=dbFilteredSet.iterator();
		Vector<String> toRemove=new Vector<String>(); 
		while(itFiltered.hasNext()){
			String apIdentifier=itFiltered.next();
			if(!actualDataDB.containsKey(apIdentifier)){
				//System.out.println("FilterManager: remove "+apIdentifier);
				toRemove.addElement(apIdentifier);
			}
		}
		for(int i=0;i<toRemove.size();i++){
			filteredDataDB.remove(toRemove.elementAt(i));
		}	
		
		// effettuo la previsione iterando su ogni elemento di db
		//synchronized(db){
			Set<String> dbActualSet=actualDataDB.keySet();
			Iterator<String> itActual=dbActualSet.iterator();
			while(itActual.hasNext()){
				String bss=itActual.next();
				Vector<Double> vRssi=actualDataDB.get(bss);
				
				// recupero vPrev di bss (se non esiste lo costruisco)
				Vector<Double> vPrev;
				if(!filteredDataDB.containsKey(bss)){
					vPrev=new Vector<Double>();
					for(int x=0;x<iteration;x++){
						vPrev.addElement(null);
					}
					filteredDataDB.put(bss,vPrev);
				}
				else{
					vPrev=filteredDataDB.get(bss);
				}
				
				// costruisco un vettore con gli ultimi "nPrev" valori
				Vector<Double> valoriPerPredizione=new Vector<Double>();
				int plus;
				if(numeroValori>=vRssi.size()){
					// there are not enough values
					plus=0;
				}
				else{
					plus=vRssi.size()-numeroValori;
				}
				
				for(int x=plus;x<vRssi.size();x++){
					// se un valore e' null, elimino tutti i valori precedenti
					if(vRssi.elementAt(x)==null){
						valoriPerPredizione=new Vector<Double>();
					}
					else{
						valoriPerPredizione.addElement(vRssi.elementAt(x));
					}
				}
				
				// richiedo la predizione
				//System.out.println("filters "+filters);
				Double prossimo=filters.get(bss).nextValue(valoriPerPredizione);//, costante);
				vPrev.addElement(prossimo);
				if(minimizeValueSet && vPrev.size()>filterMemory){
					vPrev.remove(0);
				}
			}
			
		//}
	}
	
	/*
	/** 
   	*  Dato il MAC della BS presso cui si e' associati ed il db delle previsioni,
   	*  restituisce l'elenco delle BS verso cui il nodo mobile si sta avvicinando.
   	*/
	/*private Vector<Location> confrontaSegnali(Location actualLocation,String relazione,int maxLocalitaPredette){
		Vector<Location> to=new Vector<Location>();
			
		// calcolo subito i parametri del bss attuale
		Vector vPrev=(Vector)filteredDataDB.get(actualLocation.getId());
		
		if(vPrev==null||vPrev.lastElement()==null)return to;
		
		float ultimoPrevAtt=((Float)vPrev.lastElement()).floatValue();
		
		// ora calcolo i parametri delle altre bss
		synchronized(filteredDataDB){
			Set dbPrevSet=filteredDataDB.keySet();
			Iterator it=dbPrevSet.iterator();
			//int i=0;
			while(it.hasNext()){
				String locationId=(String)it.next();
				if(!locationId.equals(actualLocation.getId())){
					vPrev=(Vector)filteredDataDB.get(locationId);
					
					if(vPrev!=null && vPrev.lastElement()!=null){
						
						float ultimoPrev=((Float)vPrev.lastElement()).floatValue();
						if(relazione.equals(">")){
							if(ultimoPrev>=ultimoPrevAtt){
								to.addElement(new it.unibo.lia.location.LocationOss(locationId,""+ultimoPrev));
							}
						}
						else{
							if(ultimoPrev<=ultimoPrevAtt){
								to.addElement(new it.unibo.lia.location.LocationOss(locationId,""+ultimoPrev));
							}
						}
					}
				}
			}
			
			LocationOss[] localitaPiuForti= new LocationOss[to.size()];
			for(int i=0;i<to.size();i++){
				localitaPiuForti[i]=(LocationOss)to.elementAt(i);
			}
			//ordino dal maggiore al minore
			if(relazione.equals(">")){
				Arrays.sort(localitaPiuForti,new Comparator<LocationOss>(){
					public int compare(LocationOss o1,LocationOss o2){
						LocationOss lo1=(LocationOss)o1;
						LocationOss lo2=(LocationOss)o2;
						if(lo1.getVal()<lo2.getVal())return 1;
						else if(lo1.getVal()==lo2.getVal())return 0;
						else return -1;
						}
					}
				);
			}
			else{
				Arrays.sort(localitaPiuForti,new Comparator<LocationOss>(){
					public int compare(LocationOss o1,LocationOss o2){
						LocationOss lo1=(LocationOss)o1;
						LocationOss lo2=(LocationOss)o2;
						if(lo1.getVal()>lo2.getVal())return 1;
						else if(lo1.getVal()==lo2.getVal())return 0;
						else return -1;
						}
					}
				);
			}
			to.clear();
			for(int x=0;x<maxLocalitaPredette&&x<localitaPiuForti.length;x++){
				to.addElement(new it.unibo.lia.location.Location(localitaPiuForti[x].getId()));
			}
		}
			
		return to;
	}*/

	/*public void reduceDataAmount(int dimMax){
		Set<String> dbSet=db.keySet();
		Iterator it=dbSet.iterator();
		Vector<String> daEliminare=new Vector<String>();
		while(it.hasNext()){
			String bss=(String)it.next();
			Vector vettorBss=(Vector)(db.get(bss));
			while(vettorBss.size()>dimMax){
				vettorBss.remove(0);
			}
			if(vettorBss.lastElement()==null){
				//db.remove(bss);
				daEliminare.addElement(bss);
			}
		}
		for(int i=0;i<daEliminare.size();i++){
			db.remove(daEliminare.elementAt(i));
		}
	}*/
}